using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace ITC_KEYBOARD
{
    /// <summary>
    /// a class to manage multikeys
    /// </summary>
    public class CMultiKeys
    {
        
        //private MultiKeyStruct[] _MultiKeyStructs;
        private CUSBkeys.usbKeyStructShort[] _MultiKeyStructs;

        private int _multiKeyCount = 0;
        private List<byte[]> _MultiKeyStructList;

        //private MultiKeyStruct[][] _MultiKeys;
        private CUSBkeys.usbKeyStructShort[][] _MultiKeys;

        /// <summary>
        /// a Multikeystruct is similar to an USBKeyStruct except to that
        /// it does not have an USB code page and USB HID scancode
        /// </summary>
        [Obsolete("Please use CUSBkeys.usbKeyStructShort instead")]
        [Serializable]
        [StructLayout(LayoutKind.Sequential)]
        public struct MultiKeyStruct
        {
            /// <summary>
            /// high byte flags for the key definition
            /// </summary>
            public byte bFlagHigh;
            /// <summary>
            /// mid byte flags for the key definition
            /// </summary>
            public byte bFlagMid;
            /// <summary>
            /// low byte flags for the key definition
            /// </summary>
            public byte bFlagLow;
            /// <summary>
            /// the value generated by the key entry, a VKEY or an index into another table
            /// </summary>
            public byte bIntScan;
            public byte[] toByteArray()
            {
                
                byte[] bOut = new byte[4];
                bOut[0] = bFlagHigh;
                bOut[1] = bFlagMid;
                bOut[2] = bFlagLow;
                bOut[3] = bIntScan;
                return bOut;
            }
            public CUSBkeys.usbKeyStructShort toUsbKeyStructShort()
            {
                CUSBkeys.usbKeyStructShort usbShort= new CUSBkeys.usbKeyStructShort();
                usbShort.bFlagHigh= (CUsbKeyTypes.usbFlagsHigh)bFlagHigh;
                usbShort.bFlagMid = (CUsbKeyTypes.usbFlagsMid)bFlagMid;
                usbShort.bFlagLow = (CUsbKeyTypes.usbFlagsLow)bFlagLow;
                usbShort.bIntScan = bIntScan;
                return usbShort;
            }
       }
        /*
        setKeyToMultiKey: 07,1E,00,42,04,31 '1' [NoFlag,NoRepeat, NoChord,MultiKeyIndex,] 'n' 'MultiIndex'->08,40,00,01 'F9' | 00,40,00,15 'q' | 
        setKeyToMultiKey: 07,1F,00,42,04,32 '2' [NoFlag,NoRepeat, NoChord,MultiKeyIndex,] 'b' 'MultiIndex'->08,40,00,01 'F9' | 00,40,00,22 'x' | 
        setKeyToMultiKey: 07,20,00,42,04,33 '3' [NoFlag,NoRepeat, NoChord,MultiKeyIndex,] 'h' 'MultiIndex'->08,40,00,01 'F9' | 00,40,00,24 'e' | 
        setKeyToMultiKey: 07,21,00,42,04,34 '4' [NoFlag,NoRepeat, NoChord,MultiKeyIndex,] 'g' 'MultiIndex'->08,40,00,01 'F9' | 00,40,00,4D 'p' | 
        */
       /// <summary>
        /// gives a text dump reprensentation of the multikey
        /// </summary>
        /// <param name="_theBytes">the CUSBkeys.usbKeyStructShort to dump</param>
        /// <returns>a string with the meanings of the structure</returns>
        public string dumpMultiKey(CUSBkeys.usbKeyStructShort _theBytes)
        {
            string s = "";
            s += ((byte)(_theBytes.bFlagHigh)).ToString("X02");
            s += "," + ((byte)(_theBytes.bFlagMid)).ToString("X02");
            s += "," + ((byte)(_theBytes.bFlagLow)).ToString("X02");
            s += "," + _theBytes.bIntScan.ToString("X02");

            //getName does not care about extended etc...
            if ((_theBytes.bFlagMid & CUsbKeyTypes.usbFlagsMid.VKEY) == CUsbKeyTypes.usbFlagsMid.VKEY)
                s += " '" + ITC_KEYBOARD.CvkMap.getName(_theBytes.bIntScan) + "'";
            else
                s += " '" + ITC_KEYBOARD.CUSBPS2_vals.Cusbps2key.getName(_theBytes.bIntScan) + "'";
            s += " | ";
            return s;
        }
        /// <summary>
        /// gives a text dump reprensentation of the multikey
        /// </summary>
        /// <param name="iIdx">the 1-based index of the CUSBkeys.usbKeyStructShort to dump</param>
        /// <returns>a string with the meanings of the structure</returns>
        public string dumpMultiKey(int iIdx)
        {
            if (iIdx > this._multiKeyCount)
                return "na";
            //byte[] bs = _MultiKeyStructList[iIdx - 1];
            //CUSBkeys.usbKeyStructShort[] rs = RawDeserialize2(bs);
            CUSBkeys.usbKeyStructShort[] rs = _MultiKeys[iIdx-1];
            string s = "->";
            for (int i = 0; i < rs.Length; i++)
            {
                s += dumpMultiKey(rs[i]);
            }
            return s;
        }

        /// <summary>
        /// read the multikey structs at offset
        /// </summary>
        /// <param name="idx">the index to use</param>
        /// <param name="iResult">-1 = error
        /// positive values = number of structs returned</param>
        /// <returns>a number of structs</returns>
        public CUSBkeys.usbKeyStructShort[] getMultiKey(int idx, ref int iResult)
        {
            if (idx > getMultiKeyCount()){
                iResult = -1;
                return new CUSBkeys.usbKeyStructShort[0];
            }
            iResult = _MultiKeys[idx].Length;
            return _MultiKeys[idx];// _MultiKeyStructs[idx];
        }

        /// <summary>
        /// get a multikey[] list as usbKeyStructShort
        /// </summary>
        /// <param name="idx">index of items to return</param>
        /// <param name="iResult">number of returned items</param>
        /// <returns></returns>
        public ITC_KEYBOARD.CUSBkeys.usbKeyStructShort[] getStructsShort(int idx, ref int iResult)
        {
            if (idx > getMultiKeyCount()){
                iResult = -1;
                return new CUSBkeys.usbKeyStructShort[0];
            }
            iResult = _MultiKeys[idx].Length;

            //copy struct
            CUSBkeys.usbKeyStructShort[] usbKeys= new CUSBkeys.usbKeyStructShort[_MultiKeys[idx].Length];
            for (int j = 0; j < iResult; j++)
            {
                usbKeys[j]= _MultiKeys[idx][j];
            }

            return usbKeys;
        }
        public CMultiKeys()
        {
            //read number of entries
            int iCount = this.getMultiKeyCount();
            if (iCount == 0)
                throw new ArgumentNullException("Sorry, no MultiKeys supported");
            //create new arrays
            _MultiKeyStructList=new List<byte[]>(iCount+1);
            this.readAll();
        }
        /// <summary>
        /// converts an array of CUSBkeys.usbKeyStructShort to a byte array for
        /// storing into registry
        /// </summary>
        /// <param name="structData">the array to convert</param>
        /// <returns>a byte array ready to save to registry</returns>
        public byte[] RawSerialize(CUSBkeys.usbKeyStructShort[] structData)
        {
            //size
            int structSize = Marshal.SizeOf(structData[0]);
            int iRawSize = structData.Length * structSize;
            byte[] rawDatas = new byte[iRawSize];
            for (int i = 0; i < structData.Length; i++)
            {
                rawDatas[(i * structSize) + 0] = (byte) structData[i].bFlagHigh;
                rawDatas[(i * structSize) + 1] = (byte) structData[i].bFlagMid;
                rawDatas[(i * structSize) + 2] = (byte) structData[i].bFlagLow;
                rawDatas[(i * structSize) + 3] = (byte) structData[i].bIntScan;
            }
            return rawDatas;
        }
        /// <summary>
        /// converts a byte arrray as read from registry
        /// to an array of CUSBkeys.usbKeyStructShort
        /// </summary>
        /// <param name="rawData">the bytes as read from registry</param>
        /// <returns>array of CUSBkeys.usbKeyStructShort</returns>
        private static CUSBkeys.usbKeyStructShort[] RawDeserialize2(byte[] rawData)
        {
            int structSize = 4;
            int iCount = rawData.Length / structSize; //we have 4 bytes per struct
            CUSBkeys.usbKeyStructShort[] _multiStruct = new CUSBkeys.usbKeyStructShort[iCount];
            for (int i = 0; i < iCount; i++)
            {
                _multiStruct[i].bFlagHigh = (CUsbKeyTypes.usbFlagsHigh)rawData[i * structSize + 0];
                _multiStruct[i].bFlagMid = (CUsbKeyTypes.usbFlagsMid)rawData[i * structSize + 1];
                _multiStruct[i].bFlagLow = (CUsbKeyTypes.usbFlagsLow)rawData[i * structSize + 2];
                _multiStruct[i].bIntScan = rawData[i * structSize + 3];
            }
            return _multiStruct;
        }
        /// <summary>
        /// get the number of defined MultiKeys as readable from registry
        /// </summary>
        /// <returns>the number of MultiKeys</returns>
        public int getMultiKeyCount()
        {
            string regKeyb = CUSBkeys.getRegLocation() + @"\MultiKeys";
            Microsoft.Win32.RegistryKey tempKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKeyb, true);
            int i = 1;
            object o = null;
            do
            {
                try
                {
                    o = tempKey.GetValue("Multi" + i.ToString());
                }
                catch (Exception)
                {
                    o = null;
                    break;
                }
                if(o!=null)
                    i++;
            } while (o!=null);
            if (i != 0)
                return i - 1;
            else
                return 0;
        }

        private void readAll()
        {
            _multiKeyCount = this.getMultiKeyCount();
            if (_multiKeyCount == 0)
                return;
            //read all MultiKeys entries
            _MultiKeyStructs = new CUSBkeys.usbKeyStructShort[_multiKeyCount];

            string regKeyb = CUSBkeys.getRegLocation() + @"\MultiKeys";
            Microsoft.Win32.RegistryKey tempKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKeyb, true);

            _MultiKeys = new CUSBkeys.usbKeyStructShort[_multiKeyCount][];
            for (int i = 0; i < _multiKeyCount; i++)
            {
                byte[] bMultiKeys = (byte[])tempKey.GetValue("Multi" + (i+1).ToString());
                int bCount = bMultiKeys.Length / 4;

                CUSBkeys.usbKeyStructShort[] currentStruct= new CUSBkeys.usbKeyStructShort[bCount];

                _MultiKeys[i] = new CUSBkeys.usbKeyStructShort[bCount];

                _MultiKeyStructs = RawDeserialize2(bMultiKeys);
                _MultiKeyStructList.Add(bMultiKeys);

                for (int j = 0; j < bCount; j++)
                {
                    _MultiKeys[i][j]=_MultiKeyStructs[j];
                }
            }
            tempKey.Close();
            System.Diagnostics.Debug.WriteLine("MultiKeys readall finished");
        }

        public int findMultiKey(CUSBkeys.usbKeyStructShort[] multiKeys)
        {
            //try to find existing MultiKey
            //CMultiKeys cmulti = new CMultiKeys();
            int iMax = this.getMultiKeyCount();
            int iFound = -1;
            int iResult = 0;
            int compareCount = 0;
            //find existing entries for multikey
            CUSBkeys.usbKeyStructShort[] mStruct;

            //for(int i=0; i<multiKeys.Length;i++)
            //    this.dumpMultiKey(multiKeys[i]);

            //look thru all multikeys
            for (int i = 0; i < iMax; i++)
            {
                compareCount = 0;
                //read thru all multikey entries, Multi1, Multi2 ...
                mStruct = this.getMultiKey(i, ref iResult);
                //compare bytes
                if (mStruct.Length == multiKeys.Length) //need to be the same sizes
                {
                    //compare multikey structs
                    for (int j = 0; j < multiKeys.Length; j++)
                    {
                        if (mStruct[j].Equals(multiKeys[j]))
                            compareCount++;
                        else
                            break;
                    }
                    if (compareCount == multiKeys.Length)
                    {
                        iFound = i;
                        break;
                    }
                }
            }

            return iFound;
        }

        /// <summary>
        /// add a new reg entry for a multikey
        /// </summary>
        /// <param name="multiKeys">an array of key entries</param>
        /// <returns>index of new entry or -1 on failure</returns>
        public int addMultiKey(CUSBkeys.usbKeyStructShort[] multiKeys)
        {
            int iMax = getMultiKeyCount();
            byte[] bNew = new byte[multiKeys.Length*4];
            //copy the bytes to a flat byte array
            bNew = RawSerialize(multiKeys);
            //add a new multikeyentry
            string regKeyb = CUSBkeys.getRegLocation() + @"\MultiKeys";
            Microsoft.Win32.RegistryKey tempKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKeyb, true);

            tempKey.SetValue("Multi" + (iMax + 1).ToString(), bNew, Microsoft.Win32.RegistryValueKind.Binary);
            //reread all
            readAll();

            return iMax + 1;
        }
    }
}
